package bgplite.io;

import java.util.*;
import java.io.*;

import bgplite.topo.AS;

/**
 * 
 * @author pendgaft
 * 
 */
public class ASTopoParser {

	private String asRelFile;
	private String ipFile;
	private String dontPruneFile;

	private HashMap<Integer, AS> activeTopology;
	private HashMap<Integer, AS> prunedTopology;

	public ASTopoParser(String asRelationshipFile, String ipCountFile, String vitalASFile) {
		this.asRelFile = asRelationshipFile;
		this.ipFile = ipCountFile;
		this.dontPruneFile = vitalASFile;

		this.activeTopology = null;
		this.prunedTopology = null;
	}

	public HashMap<Integer, AS> getActiveTopology() {
		if (this.activeTopology == null) {
			throw new RuntimeException("Asked for active topology before parsing ran.");
		}

		return this.activeTopology;
	}

	public HashMap<Integer, AS> getPrunedTopology() {
		if (this.prunedTopology == null) {
			throw new RuntimeException("Asked for pruned topology before parsing ran.");
		}

		return this.prunedTopology;
	}

	/**
	 * Static method that builds AS objects along with the number of IP
	 * addresses they have. This function does NO PRUNING of the topology.
	 * 
	 * @param wardenFile
	 *            - a file that contains a list of ASNs that comprise the warden
	 * @return - an unpruned mapping between ASN and AS objects
	 * @throws IOException
	 *             - if there is an issue reading any config file
	 */
	public void doNetworkBuild() throws IOException {

		this.parseFile();
		System.out.println("Raw topo size is: " + this.activeTopology.size());
		this.parseIPScoreFile();
		this.doNetworkPrune();
	}

	/**
	 * Static method that parses the CAIDA as relationship files and the file
	 * that contains ASNs that make up the warden.
	 * 
	 * @param asRelFile
	 *            - CAIDA style AS relationship file
	 * @param wardenFile
	 *            - file with a list of ASNs that comprise the warden
	 * @return - an unpruned mapping between ASN and AS objects
	 * @throws IOException
	 *             - if there is an issue reading either config file
	 */
	private void parseFile() throws IOException {

		this.activeTopology = new HashMap<Integer, AS>();

		String pollString;
		StringTokenizer pollToks;
		int lhsASN, rhsASN, rel;

		BufferedReader fBuff = new BufferedReader(new FileReader(this.asRelFile));
		while (fBuff.ready()) {
			pollString = fBuff.readLine().trim();

			/*
			 * ignore blanks
			 */
			if (pollString.length() == 0) {
				continue;
			}

			/*
			 * Ignore comments
			 */
			if (pollString.charAt(0) == '#') {
				continue;
			}

			/*
			 * Parse line
			 */
			pollToks = new StringTokenizer(pollString, "|");
			lhsASN = Integer.parseInt(pollToks.nextToken());
			rhsASN = Integer.parseInt(pollToks.nextToken());
			rel = Integer.parseInt(pollToks.nextToken());

			/*
			 * Create either AS object if we've never encountered it before
			 */
			if (!this.activeTopology.containsKey(lhsASN)) {
				this.activeTopology.put(lhsASN, new AS(lhsASN));
			}
			if (!this.activeTopology.containsKey(rhsASN)) {
				this.activeTopology.put(rhsASN, new AS(rhsASN));
			}

			this.activeTopology.get(lhsASN).addRelation(this.activeTopology.get(rhsASN), rel);
		}
		fBuff.close();

	}

	/**
	 * Static method to parse the IP "score" (count) file (CSV), and add that
	 * attribute to the AS object
	 * 
	 * @param asMap
	 *            - the built as map (unpruned)
	 * @throws IOException
	 *             - if there is an issue reading from the IP count file
	 */
	private void parseIPScoreFile() throws IOException {
		BufferedReader fBuff = new BufferedReader(new FileReader(this.ipFile));
		while (fBuff.ready()) {
			String pollString = fBuff.readLine().trim();
			if (pollString.length() == 0 || pollString.charAt(0) == '#') {
				continue;
			}
			StringTokenizer tokenizerTokens = new StringTokenizer(pollString, ",");
			int tAS = Integer.parseInt(tokenizerTokens.nextToken());
			int score = Integer.parseInt(tokenizerTokens.nextToken());
			this.activeTopology.get(tAS).setIPCount(score);
		}
		fBuff.close();
	}

	/**
	 * Simple static call to do the network prune. This servers as a single
	 * entry point to prune, allowing changes to the pruning strategy.
	 * 
	 * @param workingMap
	 *            - the unpruned AS map, this will be altered as a side effect
	 *            of this call
	 * @return - a mapping between ASN and AS object of PRUNED ASes
	 */
	private void doNetworkPrune() throws IOException{
		this.pruneNoCustomerAS();
	}

	/**
	 * Static method that prunes out all ASes that have no customer ASes. In
	 * otherwords their customer cone is only themselves. This will alter the
	 * supplied AS mapping, reducing it in size and altering the AS objects
	 * 
	 * @param asMap
	 *            - the unpruned AS map, will be altered as a side effect
	 * @return - a mapping of ASN to AS object containing the PRUNED AS objects
	 */
	private void pruneNoCustomerAS() throws IOException{
		this.prunedTopology = new HashMap<Integer, AS>();

		HashSet<Integer> dontPruneSet = new HashSet<Integer>();
		BufferedReader vitalASBuffer = new BufferedReader(new FileReader(this.dontPruneFile));
		while(vitalASBuffer.ready()){
			String pollStr = vitalASBuffer.readLine().trim();
			if(pollStr.length() == 0 || pollStr.charAt(0) == '#'){
				continue;
			}
			
			dontPruneSet.add(Integer.parseInt(pollStr));
		}
		vitalASBuffer.close();
		
		
		/*
		 * Find the ASes w/o customers
		 */
		for (AS tAS : this.activeTopology.values()) {
			/*
			 * leave the all warden ASes connected to our topo
			 */
			if (dontPruneSet.contains(tAS.getASN())) {
				continue;
			}

			if (tAS.getNonPrunedCustomerCount() == 0) {
				this.prunedTopology.put(tAS.getASN(), tAS);
			}
		}

		/*
		 * Remove these guys from the asn map and remove them from their peer's
		 * data structure
		 */
		for (AS tAS : this.prunedTopology.values()) {
			this.activeTopology.remove(tAS.getASN());
			tAS.purgeRelations();
		}
	}
}
